package org.testis.gui.mvc.utilities;

import org.testis.gui.mvc.controller.Draggable;
import org.testis.utils.tree.MyDefaultMutableTreeNode;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.metal.*;
import javax.swing.tree.*;

import org.testis.utils.tree.TreeNode;

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.event.*;


/*
 * 
 * @author nurhat
 */
public class DnDJTree extends JTree
        implements TreeSelectionListener,
        DragGestureListener, DropTargetListener,
        DragSourceListener {

    /** Stores the parent Frame of the component */
    private Frame m_parent = null;
    /** Stores the selected node info */
    protected TreePath m_selectedTreePath = null;
    protected MyDefaultMutableTreeNode m_selectedNode = null;
    /** Variables needed for DnD */
    private DragSource m_dragSource = null;
    private DragSourceContext m_dragSourceContext = null;
    private Draggable m_controller;
    private static final long serialVersionUID = 74611L;

    /** Constructor
    @param root The root node of the tree
    @param parent Parent JFrame of the JTree */
    public DnDJTree(MyDefaultMutableTreeNode root,
            Frame parent,
            Draggable controller) {
        super(root);
        m_parent = parent;
        m_controller = controller;
        addTreeSelectionListener(this);

        /* ********************** CHANGED ********************** */
        m_dragSource = DragSource.getDefaultDragSource();
        /* ****************** END OF CHANGE ******************** */

        DragGestureRecognizer dgr =
                m_dragSource.createDefaultDragGestureRecognizer(
                this, //DragSource
                DnDConstants.ACTION_COPY_OR_MOVE, //specifies valid actions
                this //DragGestureListener
                );


        /* Eliminates right mouse clicks as valid actions - useful especially
         * if you implement a JPopupMenu for the JTree
         */
        dgr.setSourceActions(dgr.getSourceActions() & ~InputEvent.BUTTON3_MASK);

        /* First argument:  Component to associate the target with
         * Second argument: DropTargetListener 
         */
        DropTarget dropTarget = new DropTarget(this,
                this);

        //unnecessary, but gives FileManager look
        putClientProperty("JTree.lineStyle",
                "Angled");
        ui = (MetalTreeUI) getUI();
        setDragEnabled(true);

    }

    /** Returns The selected node */
    public MyDefaultMutableTreeNode getSelectedNode() {
        return m_selectedNode;
    }

    ///////////////////////// Interface stuff ////////////////////
    /** DragGestureListener interface method */
    @Override
    public void dragGestureRecognized(DragGestureEvent e) {
        //Get the selected node
        MyDefaultMutableTreeNode dragNode = getSelectedNode();
        if (dragNode != null) {

            //Get the Transferable Object
            TreeNode treeNode = (TreeNode) dragNode.getUserObject();
            /* ********************** CHANGED ********************** */

            //Select the appropriate cursor;
            Cursor cursor = DragSource.DefaultCopyNoDrop;
            int action = e.getDragAction();
            if (action == DnDConstants.ACTION_MOVE) {
                cursor =
                        DragSource.DefaultMoveNoDrop;
                //In fact the cursor is set to NoDrop because once an action is rejected
                // by a dropTarget, the dragSourceListener are no more invoked.
                // Setting the cursor to no drop by default is so more logical, because
                // when the drop is accepted by a component, then the cursor is changed by the
                // dropActionChanged of the default DragSource.
			/* ****************** END OF CHANGE ******************** */            //begin the drag
            }
            try {
                m_dragSource.startDrag(e,
                        cursor, (Transferable) treeNode,
                        this);
            } catch (java.awt.dnd.InvalidDnDOperationException ex) {
                System.out.println(ex.toString());
            } catch (java.lang.NullPointerException ex) {
                System.out.println(ex.toString());
            }
        }
    }

    /** DragSourceListener interface method */
    @Override
    public void dragDropEnd(DragSourceDropEvent dsde) {
    }

    /** DragSourceListener interface method */
    @Override
    public void dragEnter(DragSourceDragEvent dsde) {
        /* ********************** CHANGED ********************** */
        /* ****************** END OF CHANGE ******************** */
    }

    /** DragSourceListener interface method */
    @Override
    public void dragOver(DragSourceDragEvent dsde) {
        /* ********************** CHANGED ********************** */
        /* ****************** END OF CHANGE ******************** */
    }

    /** DragSourceListener interface method */
    @Override
    public void dropActionChanged(DragSourceDragEvent dsde) {
    }

    /** DragSourceListener interface method */
    @Override
    public void dragExit(DragSourceEvent dsde) {
    }

    /** DropTargetListener interface method - What we do when drag is released */
    @Override
    public void drop(DropTargetDropEvent e) {
        //get new parent node
        Point loc = e.getLocation();
        TreePath destinationPath = getPathForLocation(loc.x,
                loc.y);

        final String msg = testDropTarget(destinationPath,
                m_selectedTreePath);
        if (msg != null) {
            e.rejectDrop();

            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    JOptionPane.showMessageDialog(
                            m_parent,
                            msg,
                            "Error Dialog",
                            JOptionPane.ERROR_MESSAGE);
                }
            });
            return;
        }

        try {
            MyDefaultMutableTreeNode newParent =
                    (MyDefaultMutableTreeNode) destinationPath.getLastPathComponent();

            MyDefaultMutableTreeNode selectedNode = getSelectedNode();
            if (!newParent.getParent().equals(selectedNode.getParent())) {
                e.rejectDrop();
                return;
            }

            MyDefaultMutableTreeNode parentNode = selectedNode.getParent();

            int newPosition = parentNode.getIndex(
                    newParent);
            int oldPosition = parentNode.getIndex(selectedNode);
            DefaultTreeModel model = (DefaultTreeModel) getModel();
            MyDefaultMutableTreeNode currentPrevNode = null, currentNextNode =
                    null,
                    newPrevNode = null,
                    newNextNode = null;
            if (selectedNode.getPreviousSibling() != null) {
                currentPrevNode =
                        (MyDefaultMutableTreeNode) selectedNode.getPreviousSibling();
            }
            if (selectedNode.getNextSibling() != null) {
                currentNextNode = selectedNode.getNextSibling();
            }
            if (oldPosition > newPosition) {
                if (newParent.getPreviousSibling()
                        != null) {

                    newPrevNode =
                            (MyDefaultMutableTreeNode) newParent.getPreviousSibling();
                }
            } else {
                newPrevNode = newParent;
            }
            if (oldPosition < newPosition) {
                if (newParent.getNextSibling() != null) {
                    newNextNode = newParent.getNextSibling();
                }
            } else {
                newNextNode = newParent;
            }
            boolean result = m_controller.moveObject(
                    currentPrevNode,
                    currentNextNode,
                    newPrevNode,
                    newNextNode,
                    selectedNode);
//            System.out.println("currentPrevNode:" + currentPrevNode);
//            System.out.println("currentNextNode:" + currentNextNode);
//            System.out.println("newPrevNode:" + newPrevNode);
//            System.out.println("selectedNode:" + selectedNode);
//            System.out.println("newNextNode:" + newNextNode);

            if (result) {
                parentNode.insert(selectedNode,
                        newPosition);
                if (oldPosition > newPosition) {
                    parentNode.insert(newParent,
                            newPosition + 1);
                } else {
                    parentNode.insert(newParent,
                            newPosition - 1);
                }

                e.acceptDrop(DnDConstants.ACTION_MOVE);
                e.getDropTargetContext().dropComplete(true);

                model.reload(parentNode);
                TreePath parentPath = new TreePath(parentNode.getPath());
                expandPath(parentPath);
            } else {
                e.rejectDrop();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            e.rejectDrop();
        }

    } //end of method

    /** DropTaregetListener interface method */
    @Override
    public void dragEnter(DropTargetDragEvent e) {
    }

    /** DropTaregetListener interface method */
    @Override
    public void dragExit(DropTargetEvent e) {
    }

    /** DropTaregetListener interface method */
    @Override
    public void dragOver(DropTargetDragEvent e) {
        /* ********************** CHANGED ********************** */
        //set cursor location. Needed in setCursor method
        Point cursorLocationBis = e.getLocation();
        TreePath destinationPath =
                getPathForLocation(cursorLocationBis.x,
                cursorLocationBis.y);


        // if destination path is okay accept drop...
        if (testDropTarget(destinationPath,
                m_selectedTreePath) == null) {
            e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
        } // ...otherwise reject drop
        else {
            e.rejectDrag();
        }
        /* ****************** END OF CHANGE ******************** */
    }

    /** DropTaregetListener interface method */
    @Override
    public void dropActionChanged(DropTargetDragEvent e) {
    }

    /** TreeSelectionListener - sets selected node */
    @Override
    public void valueChanged(TreeSelectionEvent evt) {
        m_selectedTreePath = evt.getNewLeadSelectionPath();
        if (m_selectedTreePath == null) {
            m_selectedNode = null;
            return;
        }
        m_selectedNode =
                (MyDefaultMutableTreeNode) m_selectedTreePath.getLastPathComponent();
    }

    /** Convenience method to test whether drop location is valid
    @param destination The destination path 
    @param dropper The path for the node to be dropped
    @return null if no problems, otherwise an explanation
     */
    private String testDropTarget(TreePath destination,
            TreePath dropper) {
        //Typical Tests for dropping

        //Test 1.
        boolean destinationPathIsNull = destination == null;
        if (destinationPathIsNull) {
            return "Invalid drop location.";        //Test 2.
        }
        MyDefaultMutableTreeNode node =
                (MyDefaultMutableTreeNode) destination.getLastPathComponent();
        if (!node.getAllowsChildren()) {
            return "This node does not allow children";
        }
        if (destination.equals(dropper)) {
            return "Destination cannot be same as source";        //Test 3.
        }
        if (dropper.isDescendant(destination)) {
            return "Destination node cannot be a descendant.";        //Test 4.
        }
        if (dropper.getParentPath().equals(destination)) {
            return "Destination node cannot be a parent.";
        }
        return null;
    }
}
